//////////////////////////////////////////////////////////////////////////

#ifndef _BOOSTSERVERCONNECTIONIMPL_H_
#include "boostclientconnectionimpl.h"
#endif

//////////////////////////////////////////////////////////////////////////

#ifndef _CONNECTIONPACKET_H_
#include "connectionpacket.h"
#endif

#include <boost/bind.hpp>
#include <iostream>

//////////////////////////////////////////////////////////////////////////


BoostClientConnectionImpl::BoostClientConnectionImpl()
// Initialize TCP socket
: m_TCPSocket(m_IOService), m_UDPSocket(m_IOService)
{
}


//////////////////////////////////////////////////////////////////////////


BoostClientConnectionImpl::~BoostClientConnectionImpl()
{
}


//////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::SendTCP( const Packet& _packet )
{
    // Wrap packet to connection packet
    ConnectionPacket cp(_packet);

    // Pack connection packet code
    cp.PackCode();

    // Send packet
    BoostSendTCP(cp);
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::BoostSendTCP( const ConnectionPacket& _packet )
{
    // Send data through TCP
    m_TCPSocket.async_send( boost::asio::buffer( _packet.Data(), _packet.Size() ),
        boost::bind( &BoostClientConnectionImpl::HandleSendTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    // Run io_service thread
    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::SendUDP( const Packet& _packet )
{
    // Wrap packet to connection packet
    ConnectionPacket cp(_packet);

    // Pack connection packet code
    cp.PackCode();

    // Add packet to send queue
    AddPacketToSendQueue(cp);

    // Start send packets
    StartSendUDP();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::AddPacketToSendQueue( const ConnectionPacket& _packet )
{
    // Lock packet queue
    m_PacketQueueMutex.Lock();

    // Add packet
    m_PacketQueue.push(_packet);

    // Unlock packet queue
    m_PacketQueueMutex.Unlock();
}


////////////////////////////////////////////////////////////////////////////////


ConnectionPacket BoostClientConnectionImpl::GetPacketFromSendQueue()
{
    // Lock packet queue
    m_PacketQueueMutex.Lock();

    // Get the first packet and remove it from queue
    ConnectionPacket cp = m_PacketQueue.front();
    m_PacketQueue.pop();

    // Unlock packet queue
    m_PacketQueueMutex.Unlock();

    // Return the packet
    return cp;
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::StartSendUDP()
{
    try
    {
        // Try to resolve client endpoint
        udp::endpoint sendEndpoint = GetClientEndpoint(this);

        // Get packet from queue and send it
        ConnectionPacket cp = GetPacketFromSendQueue();
        BoostSendUDP(cp, sendEndpoint);
    }
    catch (const char* _e)
    {
        // Client endpoint has not been resolved
        // Request it from client
        RequestClientsEndpoint();
    }
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::BoostSendUDP( const ConnectionPacket& _packet, const udp::endpoint& _endpoint )
{
    // Send data through UDP
    GetUDPSocket()->async_send_to( boost::asio::buffer( _packet.Data(), _packet.Size() ),
        _endpoint,
        boost::bind( &BoostClientConnectionImpl::HandleSendUDP,
        this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    // Run io_service thread
    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::StartReceiveTCP()
{
    // Recv data through TCP
    m_TCPSocket.async_receive( boost::asio::buffer(m_RecvBufferTCP, MAX_BUFFER_SIZE),
        boost::bind( &BoostClientConnectionImpl::HandleRecvTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    // Run io_service thread
    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::StartReceiveUDP()
{
    // Recv data through UDP
    GetUDPSocket()->async_receive_from( boost::asio::buffer(m_RecvBufferUDP, MAX_BUFFER_SIZE),
        m_UDPRecvEndpoint,
        boost::bind( &BoostClientConnectionImpl::HandleRecvUDP,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    // Run io_service thread
    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::HandleSendTCP( const error_code& _error,
                                              size_t _bytesTransferred )
{
    if (!_error)
        HandleSendTCP(_bytesTransferred);
    else
        std::cout << "ERROR SEND TCP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////



void BoostClientConnectionImpl::HandleSendUDP( const error_code& _error,
                                              size_t _bytesTransferred )
{
    if (!_error)
        HandleSendUDP(_bytesTransferred);
    else
        std::cout << "ERROR SEND UDP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::HandleRecvTCP( const error_code& _error,
                                              size_t _bytesTransferred )
{
    if (!_error)
    {
        // Compose a connection packet from received data
        ConnectionPacket cp(m_RecvBufferTCP, _bytesTransferred);

        // Unpack code
        cp.UnPackCode();

        // Handle received packet
        HandleRecvTCP(cp);

        // Start receive new tcp packets
        StartReceiveTCP();
    }
    else
        std::cout << "ERROR RECV TCP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::HandleRecvUDP( const error_code& _error, size_t _bytesTransferred )
{
    if (!_error)
    {
        // Try to find connection by endpoint
        ConnectionsByUDPEndpoints::iterator it = m_ConnectionsByUDPEndpoints.find(m_UDPRecvEndpoint);

        if ( it != m_ConnectionsByUDPEndpoints.end() )
        {
            // Connection found
            BoostClientConnectionImpl* conn = it->second;

            // Create a connection packet from received data
            ConnectionPacket cp(m_RecvBufferUDP, _bytesTransferred);

            // Unpack code
            cp.UnPackCode();

            // Handle received packet
            conn->HandleRecvUDP(cp);
        }
        else
        {
            // New udp connection!

            // Compose a connection packet
            ConnectionPacket cp(m_RecvBufferUDP, _bytesTransferred);

            // Connection packet code must be a client endpoint data!
            if ( cp.GetPacketCode() == ConnectionPacket::CLIENT_ENDPOINT_DATA )
            {
                // Unpack code
                cp.UnPackCode();

                // Get client tcp endpoint
                std::string clientEndpoint( cp.Data(), cp.Size() );

                // Find connection by it
                ConnectionsByTCPEndpoints::iterator it = m_ConnectionsByUDPEndpoints.find(clientEndpoint);
                if ( it != m_ConnectionsByUDPEndpoints.end() )
                {
                    // Connection found
                    BoostConnectionImpl* conn = *it;

                    // Map client UDP endpoint to this connection
                    m_EndpointsByConnections[conn] = m_UDPRecvEndpoint;
                }
            }
        }


        // After all start receiving UDP again...
        StartReceiveUDP();
    }


    // WSAGetLastError =)
    else if (_error.value() == 10022 || _error.value() == 10061)
        // Ignore it
        StartReceiveUDP();

    // Some other problem
    else
        std::cout << "ERROR RECV UDP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::Connect( const char* _address )
{
    // Resolve address
    tcp::resolver resolver( *GetIOService() );
    tcp::resolver::query query(_address, m_PortStr);
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    tcp::resolver::iterator end;

    error_code error = boost::asio::error::host_not_found;
    while (error && endpoint_iterator != end)
    {
        m_TCPSocket.close();
        m_TCPEndpoint = *endpoint_iterator;

        // Connect to server
        m_TCPSocket.connect(m_TCPEndpoint, error);
        ++endpoint_iterator;
    }
    if (!error)
    {
        // Also establish UDP connection
        udp::resolver resolver( *GetIOService() );
        udp::resolver::query query( udp::v4(), _address, m_PortStr );
        m_UDPRecvEndpoint = *resolver.resolve(query);
        m_UDPSendEndpoint = m_UDPRecvEndpoint;

        GetUDPSocket()->open( udp::v4() );
        GetUDPSocket()->send_to(boost::asio::buffer("connected", 9), m_UDPSendEndpoint);

        StartReceiveUDP();
        StartReceiveTCP();
    }
    else
    {
        std::cout << "ERROR CONNECT: " << error.message() << std::endl;
        throw boost::system::system_error(error);
    }
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::Close()
{
    m_TCPSocket.close();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::HandleAccept( const error_code& _error )
{
    if (!_error)
    {
        // Save tcp endpoint to string
        std::string tcpEndpoint;
        tcpEndpoint << m_TCPSocket.remote_endpoint();

        // Save connection by tcp endpoint
        m_ConnectionsByTCPEndpoints[tcpEndpoint] = this;

        if (!m_UDPAcceptStarted)
        {
            // Also accept UDP packets
            GetUDPSocket()->open( udp::v4() );
            GetUDPSocket()->bind( udp::endpoint(udp::v4(), m_Port) );
            StartReceiveUDP();
            m_UDPAcceptStarted = true;
        }

        // Start receive tcp packets
        StartReceiveTCP();

        // Handle accept
        HandleAccept();
    }
    else
        std::cout << "ERROR ACCEPT: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


io_service* BoostClientConnectionImpl::GetIOService()
{
    if (!m_IOService)
        m_IOService = new io_service;

    return m_IOService;
}


////////////////////////////////////////////////////////////////////////////////


tcp::acceptor* BoostClientConnectionImpl::GetTCPAcceptor()
{
    if (!m_Acceptor)
        m_Acceptor = new tcp::acceptor( *GetIOService(), tcp::endpoint(tcp::v4(), m_Port) );

    return m_Acceptor;
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::Run()
{
    if (!m_Started)
        m_Thread = boost::thread(&BoostClientConnectionImpl::ThreadProc);
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::ThreadProc()
{
    m_Started = true;
    GetIOService()->run();
    m_Started = false;
}


////////////////////////////////////////////////////////////////////////////////


udp::socket* BoostClientConnectionImpl::GetUDPSocket()
{
    if (!m_UDPSocket)
        m_UDPSocket = new udp::socket( *GetIOService() );

    return m_UDPSocket;
}


////////////////////////////////////////////////////////////////////////////////
